﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.run;
using xplang.abstractions;
using xplang.errors;
using xplang.statements;
using xplang.contexts;
using xplang.fs;

namespace xplang.parse
{

  public abstract class Compiler
  {

    public Token current;
    protected List<ImplementationCode> implementationCodes = new List<ImplementationCode>();

    public abstract void nextToken();

    protected bool isEof()
    {
      return (current == null) || (current.type == TokenType.ttEOF);
    }

    protected void checkEof(bool expected)
    {
      if (expected && !isEof())
        new EofExpectedError(current);
      else if (!expected && isEof())
        new EofUnexpectedError(current);
    }

    protected bool isIdentifier()
    {
      return current != null && current.type == TokenType.ttIdentifier;
    }

    protected Token readIdentifier()
    {
      if (current == null || current.type != TokenType.ttIdentifier)
      {
        new IdentifierExpectedError(current);
        return null;
      }
      Token t = current;
      nextToken();
      return t;
    }

    protected bool isOperator()
    {
      return current != null && current.type == TokenType.ttOperator;
    }

    protected bool isOperator(String op)
    {
      return current != null && current.type == TokenType.ttOperator && current.text == op;
    }

    protected Token readOperator()
    {
      if (current == null || current.type != TokenType.ttOperator)
        new OperatorExpectedError(current);
      Token t = current;
      nextToken();
      return t;
    }

    protected bool readOperator(String op)
    {
      if (current == null || current.type != TokenType.ttOperator || current.text != op)
      {
        new OperatorExpectedError(current, op);
        return false;
      }
      nextToken();
      return true;
    }

    protected bool checkOperator(String op)
    {
      bool isOp = isOperator(op);
      if (!isOp)
        new OperatorExpectedError(current, op);
      return isOp;
    }

    protected bool isClause(String clause)
    {
      return current != null && current.type == TokenType.ttClause && current.text == clause;
    }

    protected bool tryCompileTypeReference(ClassDecl declaring, ref ClassRef c)
    {
      if (!isIdentifier())
        return false;
      Token ident = current;

      if (declaring.genericParams != null)
      {
        GenericParamDecl p = declaring.genericParams.SingleOrDefault(g => g.name == ident.text);
        if (p != null)
        {
          c = p.getRef();
          nextToken();
          return true;
        }
      }

      ClassDecl refdecl = declaring.context.findClassDecl(ident.text);
      if (refdecl == null)
        return false;

      List<ClassRef> refs = new List<ClassRef>();
      if (isOperator("<"))
      {
        do
        {
          nextToken();
          ClassRef p = compileTypeReference(declaring);
          refs.Add(p);
        } while (isOperator(","));
      }
      declaring.context.registerUsing(declaring, refdecl);
      c = refdecl.getRef(refs.ToArray());
      nextToken();
      // todo qident
      return true;
    }

    protected ClassRef compileTypeReference(ClassDecl declaring)
    {
      ClassRef c = null;
      if (!tryCompileTypeReference(declaring, ref c))
        new TypeExpectedError(current);
      return c;
    }

    public Modifier compileModifiers()
    {
      Modifier r = Modifier.None;
      bool repeat = true;
      while (repeat)
      {
        if (isClause("static"))
          r |= Modifier.Static;
        else
          return r;
        if (repeat)
          nextToken();
      };
      return r;
    }

    public Variable[] compileMethodParams(Token name)
    {
      List<Variable> parameters = new List<Variable>();
      checkOperator("(");
      nextToken();
      checkOperator(")");
      nextToken();
      return parameters.ToArray();
    }

    public void compileMethodImplementation(ClassDecl declaringClass, Token name, CompiledClassMethod method, CompilerForExpressionSupport support)
    {
      checkOperator("{");
      assignImplementation("{", "}", true,
        delegate(ImplementationCode c)
        {
          return new CompilerForStatements(method, declaringClass, name, null, c, support);
        },
        delegate(ImplementationCode c)
        {
          method.registerImplementation(declaringClass, (StatementBlock)c.code);
        }
        );
    }

    public void assignImplementation(string startOperator, string stopOperator, bool includeDelimiters, GetCompilerForImplementatedCode getCompiler, ImplementatedCode implementatedCode)
    {
      ImplementationCode code = new ImplementationCode();
      code.getCompiler += getCompiler;
      code.implementatedCode += implementatedCode;
      code.tokens = new List<Token>();
      int level = 0;
      if (startOperator != null)
        if (isOperator(startOperator))
        {
          if (includeDelimiters)
            code.tokens.Add(current);
          nextToken();
        }
      while ((level > 0) || !isOperator(stopOperator))
      {
        if (isEof())
        {
          new EofUnexpectedError(current);
          return;
        }
        if (isOperator("{"))
          level++;
        if (isOperator("}"))
          level--;
        code.tokens.Add(current);
        nextToken();
      }

      implementationCodes.Add(code);
      if (includeDelimiters)
        code.tokens.Add(current);
      nextToken();
    }
  }
}
